Appearance
状态: 🟢 已修复
日期: 2026-07-02 严重程度: 🔴 P1 影响范围:
frontend/src/scene/perception.ts(视线追踪 / 程序化骨骼旋转) 发现方式: 开发发现 修复方案: 改写 linkedBone.rotationQuaternion(局部旋转)+ 手动触发 updateWorldMatrix 重算骨骼链 + _markAsDirty 刷新渲染矩阵
骨骼变换覆写无效(视线追踪 / 程序化骨骼旋转)
日期:2026-07-02
故障现象
- 直接写
runtimeBone.worldMatrix后,骨骼视觉上不动 - 或骨骼只有旋转变化,位置留在原地(子骨骼不跟随父骨骼)
- WASM 版
MmdWasmRuntime下尤其明显
诊断方法
步骤 1:afterRender readback 验证
在 onAfterRenderObservable 中读回 worldMatrix,与 onBeforeRender 写入值对比:
typescript
scene.onBeforeRenderObservable.add(() => {
// 写入 worldMatrix
bone.worldMatrix[0] = newValue;
console.log('[verify] write=', bone.worldMatrix[0]);
});
scene.onAfterRenderObservable.add(() => {
console.log('[verify] readback=', bone.worldMatrix[0]);
});| 结果 | 结论 |
|---|---|
| readback == write | 写入生效,问题在别处(如骨骼层级、权重) |
| readback == 旧值 | WASM 双缓冲覆盖,需切到 JS 版运行时 |
步骤 2:子骨骼位置验证
如果写入生效但子骨骼不跟随父骨骼移动,说明绕过了骨骼层级。检查是否直接写 worldMatrix 而非 linkedBone.rotationQuaternion。
根因
| 层 | 机制 | 后果 |
|---|---|---|
| WASM 双缓冲 | MmdWasmRuntime 的 worldTransformMatrices 有前后缓冲,update() 用后缓冲覆盖前缓冲 | 直接写 worldMatrix 在下一帧被还原 |
| 渲染数据源 | 渲染读 _computeTransformMatrices 输出的 targetMatrix,不直接读 worldMatrix | _markAsDirty 在 update() 末尾已执行,之后不重跑 |
| 骨骼层级 | worldMatrix = localMatrix × parentWorldMatrix,直接写 worldMatrix 不影响子骨骼的 parentWorldMatrix | 子骨骼位置不更新 |
正确做法
改 linkedBone.rotationQuaternion(局部旋转),手动触发 updateWorldMatrix 重算骨骼链,最后 _markAsDirty 刷新渲染矩阵。详见 docs/architecture.md §10.1。